package org.zoomdev.zoom.security;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.zoomdev.zoom.common.exceptions.SystemException;
import org.zoomdev.zoom.security.annotations.*;
import org.zoomdev.zoom.security.utils.SensitiveUtils;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Sensitives {

    private static final ObjectMapper mapper = new ObjectMapper();
    private static final HashMap<Class, StdSerializerFactory> CACHE_FACTORY = new HashMap<>();
    private static final ConcurrentHashMap<SensitiveData, Sensitive> SENSITIVE_DATA_CACHE = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<SensitiveTruncation, Sensitive> SENSITIVE_TRUNCATION_CACHE = new ConcurrentHashMap<>();
    public static String MASK_CHAR = "*";

    static {

        mapper.setAnnotationIntrospector(new NopAnnotationIntrospector() {
            @Override
            public Object findSerializer(Annotated am) {
                for (Map.Entry<Class, StdSerializerFactory> entry : CACHE_FACTORY.entrySet()) {
                    Class type = entry.getKey();
                    if (am.hasAnnotation(type)) {
                        Annotation annotation = am.getAnnotation(type);
                        return entry.getValue().create(annotation);
                    }
                }
                return null;
            }
        });

        SimpleModule module = new SimpleModule();
        module.addSerializer(new ByteSerializer());

        mapper.registerModule(module);

        register(SensitiveSkip.class, s -> {
            if (s == null) return null;
            return "...";
        });
        register(SensitivePhone.class, SensitiveUtils::phone);
        register(SensitiveBankCard.class, SensitiveUtils::bankCard);
        register(SensitiveChineseName.class, SensitiveUtils::chineseName);
        register(SensitiveIdCard.class, SensitiveUtils::idCard);
        register(SensitiveAddress.class, SensitiveUtils::address);
        register(SensitiveEmail.class, SensitiveUtils::email);
        registerFactory(SensitiveTruncation.class, annotation -> SENSITIVE_TRUNCATION_CACHE.computeIfAbsent(annotation, k -> new Sensitive() {
            private final int len = k.value();

            @Override
            public String to(String s) {
                return SensitiveUtils.truncate(s, len);
            }
        }));
        registerFactory(SensitiveData.class, annotation -> SENSITIVE_DATA_CACHE.computeIfAbsent(annotation, k -> new Sensitive() {
            private final int keepFirst = k.keepFirst();
            private final int keepLast = k.keepLast();

            @Override
            public String to(String s) {
                if (s == null) {
                    return null;
                }
                return SensitiveUtils.defaultSensitive(s, keepFirst, keepLast, MASK_CHAR);
            }
        }));
    }

    private Sensitives() {
    }

    public static ObjectMapper getMapper() {
        return mapper;
    }

    public static <T> void registerFactory(Class<T> annotationClass, SensitiveFactory<T> factory) {
        CACHE_FACTORY.put(annotationClass, annotation -> new StdSensitive(factory.create((T) annotation)));
    }

    public static void register(Class annotationClass, Sensitive sensitive) {
        CACHE_FACTORY.put(annotationClass, new StdSerializerFactory() {
            private final StdSerializer<String> serializer = new StdSensitive(sensitive);

            @Override
            public StdSerializer<String> create(Object annotation) {
                return serializer;
            }
        });
    }


    public static String toSensitiveString(Object data) {
        try {
            return mapper.writeValueAsString(data);
        } catch (JsonProcessingException e) {
            throw new SystemException("json转化失败", e);
        }
    }

    private interface StdSerializerFactory {
        StdSerializer<String> create(Object annotation);
    }

    private static class StdSensitive extends StdSerializer<String> {
        private final Sensitive sensitive;

        protected StdSensitive(Sensitive sensitive) {
            super(String.class);
            this.sensitive = sensitive;
        }

        @Override
        public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
            if (s == null) {
                jsonGenerator.writeNull();
            } else {
                jsonGenerator.writeString(sensitive.to(s));
            }
        }
    }

    private static class ByteSerializer extends StdSerializer<byte[]> {

        private static final long KB = 1024;
        private static final long MB = KB * 1024;
        private static final long GB = MB * 1024;
        protected ByteSerializer() {
            super(byte[].class);
        }

        @Override
        public void serialize(byte[] s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
            if (s == null) {
                jsonGenerator.writeNull();
            } else {
                jsonGenerator.writeString(getLengthString(s.length));
            }
        }

        private static String getLengthString(int len) {
            if (len <= 0) {
                return "Bytes: 0 bytes";
            }

            if (len >= GB) {
                return formatSize(len, GB, "GB");
            } else if (len >= MB) {
                return formatSize(len, MB, "MB");
            } else if (len >= KB) {
                return formatSize(len, KB, "KB");
            } else {
                return "Bytes: " + (len + (len == 1 ? " byte" : " bytes"));
            }
        }

        private static String formatSize(long size, long unit, String unitName) {
            double value = (double) size / unit;

            // 根据数值大小决定精度
            if (value >= 100) {
                return String.format("Bytes: %.0f %s", value, unitName);
            } else if (value >= 10) {
                return String.format("Bytes: %.1f %s", value, unitName);
            } else {
                return String.format("Bytes: %.2f %s", value, unitName);
            }
        }
    }
}
